# encoding: utf-8
"""
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    created by siyi.chen on '09/10/2024'
    comment: 意图检测
"""
import asyncio
import datetime
import re

from project.lib.decorator import retry
from project.model.basic import User
from project.service.agents.agent import Agent
from project.service.common import CommonService
from project.model.flow import Flow
from project.core.function import match_texts


class NluService:

    @staticmethod
    @retry()
    async def detect_intention(user_message, chat_model_name='moonshot-v1-32k'):
        """ 推测用户意图
        :param user_message: 用户消息
        :param chat_model_name: 模型名称
        :return:
        """
        prompt = f"""
            ###Instruction
            请判断以下用户当前消息符合以下哪一个意图？请按输出格式要求输出，并输出你的推理过程。

            # 意图列表
            意图编号：video_link 意图内容：用户明确提到要视频通话或联系前台。
            意图编号：report 意图内容：用户要查看自己的睡眠报告。
            意图编号：other 意图内容：其他意图。

            # 输出格式
            用户对你说："...这里是用户对你说的话..." <reason>你的推理过程：...</reason><intent>意图编号</intent>

            # 输出样例
            用户对你说："我想联系前台" <reason> 用户明确提到要联系前台</reason><intent>video_link</intent>
            用户对你说："我想看看我的睡眠报告" <reason>用户明确提到要查看自己的睡眠报告</reason><intent>report</intent>
            用户对你说："我昨天睡的怎么样？" <reason>可能用户在关心自己的睡眠数据，推出睡眠报告</reason><intent>report</intent>
            用户对你说："晚饭吃了吗？" <reason>用户的意图不在意图列表里，属于其他意图</reason><intent>other</intent> 

            ###Output
            用户对你说："{user_message}"
        """
        aget = Agent(user=User(), user_message=user_message, chat_model_name=chat_model_name, business_name='用户意图(视频、报告)')
        response = await aget.async_interact(prompt)
        res = match_texts(response, 'reason', 'intent', 'message')
        # 无意图返回
        if not res.get('intent') or res.get('intent') not in ['video_link', 'report']:
            return []
        res['response'] = res.pop('message', '')

        return res

    @staticmethod
    async def inference_user_date_async(user_message, chat_model_name='moonshot-v1-32k'):
        """ 推理健康报告日期
        :param user_message:
        :param chat_model_name: 模型名称
        :return:
        """
        prompt = f"""
            请判断用户当前消息指的是"哪一天" 或是 "哪些天"？以#基准日为基准进行判断。 请按输出格式要求输出，并输出你的推理过程。
            <大前天>是指#基准日的前三天
            <大后天>是指#基准日的后三天

            #用户当前消息
            {user_message}

            #基准日
            {datetime.datetime.now().strftime('%Y-%m-%d')}

            # 输出格式：
            当<文本>中包含跟日期时间有关信息时： <reason>你的推理过程...</reason><result>日期字符串('%Y-%m-%d')列表</result>
            当<文本>中不包含日期时间有关信息时：<reason>你的推理过程...</reason><result>[]</result>

            # 输出样例
            用户对你说："我昨晚睡的怎样？" <reason>用户提到了"昨晚", 就是指前一天晚上。如果当天基准日是2024-07-04，则前一天就是2024-07-03</reason><result>[\'2024-07-03\']</result>
            用户对你说："我大前天上课了？" <reason>用户提到了"大前天", 就是指基准日的前三天。如果当天基准日是2024-07-04，则前一天就是2024-07-01</reason><result>[\'2024-07-01\']</result>
            用户对你说："给我看下7月1号的睡眠报告" <reason>用户提到了"7月1号", 非常明确的日期信息, 不需要通过基准日计算就能确定用户说的是7月1号</reason><result>[\'2024-07-01\']</result>
            用户对你说："7月10日的睡眠报告能给我看看吗" <reason>用户提到了"7月10号", 非常明确的日期信息, 不需要通过基准日计算就能确定用户说的是7月10号</reason><result>[\'2024-07-10\']</result>

        """
        aget = Agent(user=User(), user_message=user_message, chat_model_name=chat_model_name, business_name='推测日期')
        response = await aget.async_interact(prompt)
        data = match_texts(response, 'reason', 'result')

        result = data.get('result')
        if result and isinstance(result, str):
            result = CommonService.llm_response_handler(result.replace('[]{}', ''))

        if not result:
            result = re.findall(r"\['\d{4}-\d{2}-\d{2}'\]", data.get('gpt_logic', ''))
            if result:
                result = eval(result[-1])

        data['result'] = result or []

        return data

    @staticmethod
    async def detect_service(user: User,
                             user_message, flows: list[Flow],
                             chat_model_name='',
                             agent_profile='',
                             user_role=''):
        """ 推荐用户服务
        :param user: 用户
        :param user_message: 用户消息
        :param flows: 服务流程列表
        :param chat_model_name: 模型名称
        :param agent_profile:
        :param user_role:
        :return:
        """
        services = [v.service_agent_desc() for v in flows]
        services.append({'服务名称': '追问', '服务描述': f'不确定{user_role}是否需要某项服务时，进行追问。'})

        instruction = """
            ### Instruction
                {agent_profile}，请仔细阅读以下{user_role}的最新消息，并结合你和{user_role}的对话记录，判断{user_role}需要你在对话记录里推荐的什么服务？或者{user_role}同意了你推荐的某项服务？或者{user_role}明确指出了需要某项服务？                
                如果客人回复表示肯定，那请结合你们的对话记录找一找是不是客人同意了你推荐的某项服务。
                1）如果{user_role}明确指定某个服务，且该服务在你的服务列表内，请列出服务列表中对应的服务名称；
                2）如果{user_role}没有明确提到任何服务，或提出的服务不在你的服务列表之内，请写"无"；比如{user_role}只是问一些问题，或者表达一些感受，而不是明确告诉你要你做什么，那这个时候不需要给{user_role}推荐或匹配服务，因为{user_role}没有"明确"提出服务需求。
                3）注意返回一项{user_role}最新需求的服务，并输出你的推理过程；
                只有当{user_role}明确提到要某个服务的时候才会输出该服务！！！记住了！你超级聪明的！不会搞错的！

                ### {user_role}的最新消息：
                {user_message}

                ### 你和{user_role}的对话记录：
                {user_memories}

                ### 你可以提供的服务：
                {services}

                ### 错误的输出举例：
                1.错误输出：
                '''
                服务: 订餐服务
                推理: {user_role}的最新消息询问了酒店的餐饮服务，虽然{user_role}没有直接提到订餐服务，但根据之前的对话记录，{user_role}曾表示想订午餐，并且我方已经提供了酒店的餐饮服务信息，包括订餐服务。因此，可以推断{user_role}可能需要订餐服务。
                '''
                错误原因：
                {user_role}曾表示想订午餐，不代表当前{user_role}想订午餐，当前{user_role}最新消息只是在询问酒店的餐饮服务，对应的应该是问答，因此不应该与订餐服务对应，所以是回答'chat'。

                2.错误输出：
                '''
                服务: 物品配送
                推理: {user_role}的最新消息询问了“可以寄快递吗”，这表明{user_role}需要将物品寄出，而酒店提供的服务中，“物品配送”服务可以满足{user_role}将物品寄出的需求。虽然{user_role}没有直接提到“物品配送”这个服务名称，但根据{user_role}询问的内容，可以推断出{user_role}需要的服务是将物品寄出，这与“物品配送”服务的描述相匹配。因此，根据{user_role}的询问内容，可以判断{user_role}需要的服务是“物品配送”。
                '''
                错误原因：
                {user_role}需要将物品寄出，酒店提供的“物品配送”服务是向用户配送酒店的用品，无法满足{user_role}将物品寄出的需求，因此不应该与场所预定服务对应，所以是回答'chat'。
                
                3.错误输出:
                '''
                服务: 报障维修
                推理: {user_role}提到拖鞋坏了，穿不了，这符合报障维修服务的描述。
                '''
                错误原因：
                {user_role}提到拖鞋坏了，穿不了。拖鞋属于日常生活用品范围，更符合“用品补充”的服务范围。而“报障维修”更属于房间电器、家具等方面的维修服务。所以应该是“用品补充”。

                ### 输出格式要求：
                请按照以下格式提供你的输出：
                服务:服务名称
                推理:你的推理过程，包括你是如何根据{user_role}的消息和对话历史来判断的，不多于50字。
                ### Output
        """
        aget = Agent(user=user, user_message=user_message, chat_model_name=chat_model_name, business_name='推荐服务', agent_profile=agent_profile, user_role=user_role)
        response = await aget.async_interact(prompt=instruction, services=services)
        if not response:
            return
        intent = response.replace('：', ':').split('\n')[0].split('服务:')[-1].strip()
        if '追问' == intent.strip():
            intent = '无'
        data = None
        if intent and intent != '无':
            data = {
                'intent': 'service',
                'action': intent.replace('。', '').replace('.', '')
            }
        return data

    @staticmethod
    async def nlu(user: User, user_message, flows: list[Flow], chat_model_name='', agent_profile = '', user_role=''):
        """ 全局意图检测
        :param user: 用户
        :param user_message: 用户消息
        :param flows: 服务流程列表
        :param chat_model_name: 模型名称
        :param agent_profile:
        :param user_role:
        :return:
        """
        # 并发执行
        tasks = list()
        # 视频通话
        tasks.append(
            asyncio.create_task(NluService.detect_intention(user_message, chat_model_name=chat_model_name), name='global_intent')
        )
        # 健康报告日期
        tasks.append(
            asyncio.create_task(NluService.inference_user_date_async(user_message, chat_model_name=chat_model_name), name='report_date')
        )
        # 服务流程
        tasks.append(
            asyncio.create_task(NluService.detect_service(user, user_message, flows, chat_model_name=chat_model_name, agent_profile = agent_profile, user_role=user_role), name='service')
        )

        await asyncio.gather(*tasks)

        global_intent = {}
        report_date = ''
        service = {}
        for task in tasks:

            task_name = task.get_name()
            task_result = task.result()

            if not task_result:
                continue
            task_result.pop('reason', None)
            task_result.pop('gpt_logic', None)

            # 酒店固定问答
            if task_name == 'global_intent':
                global_intent = task_result

            # 日报日期
            if task_name == 'report_date':
                result = task_result.get('result', [])
                report_date = result[0] if result else datetime.datetime.now().strftime('%Y-%m-%d')

            # 服务流程
            if task_name == 'service':
                service = task_result

        if global_intent and global_intent['intent'] == 'report':
            global_intent['report_date'] = report_date

        return global_intent or service

    @staticmethod
    async def test():
        """ 推荐用户服务

        :return:
        """
        instruction = """
                
            ### Instruction
                你是一个在酒店房间里的人工智能机器人，请仔细阅读以下客人的最新消息，并结合你和客人的对话记录，判断客人明确指定要你做什么样的服务？
                1）如果客人明确指定某个服务，且该服务在你的服务列表内，请列出服务列表中对应的服务名称；
                2）如果客人没有明确提到任何服务，或提出的服务不在你的服务列表之内，请写"无"；比如客人只是问一些问题，或者表达一些感受，而不是明确告诉你要你做什么，那这个时候不需要给客人推荐或匹配服务，因为客人没有"明确"提出服务需求。
                3）注意返回一项客人最新需求的服务，并输出你的推理过程；
                只有当客人明确提到要某个服务的时候才会输出该服务！！！记住了！你超级聪明的！不会搞错的！
                
            ### 客人的最新消息：
            拖鞋怎么是坏的，穿不了？

            ### 你和客人的对话记录：
            ["对话序号:20241121203655\t\t客人对你说:拖鞋怎么是坏的，穿不了？"]

            ### 你可以提供的服务：
            ['服务名称: 用品补充。服务描述: 为用户提供的拖鞋、牙膏牙刷等生活用品更换和补充服务。', '服务名称: 外卖取送。服务描述: 用户点了外卖，需要配送至房间的服务。', '服务名称: 洗衣服务。服务描述: 用户的衣物脏了，需要清洗的服务。', '服务名称: 客房清洁。服务描述: 房间脏乱，需要保洁员整理或者打扫的服务。', '服务名称: 订餐服务。服务描述: 为用户提供的送餐服务。。', '服务名称: 发票开取。服务描述: 为用户提供的开发票服务。', '服务名称: 报障维修。服务描述: 房间内设备出现故障或者损坏，用户无法正常使用，需要预约维修人员上门进行维修。', {{'服务名称': '追问', '服务描述': '不确定客人是否需要某项服务时，进行追问。'}}]

            ### 错误的输出举例：
                1.错误输出：
                '''
                服务: 订餐服务
                推理: 客人的最新消息询问了酒店的餐饮服务，虽然客人没有直接提到订餐服务，但根据之前的对话记录，客人曾表示想订午餐，并且我方已经提供了酒店的餐饮服务信息，包括订餐服务。因此，可以推断客人可能需要订餐服务。
                '''
                错误原因：
                客人曾表示想订午餐，不代表当前客人想订午餐，当前客人最新消息只是在询问酒店的餐饮服务，对应的应该是问答，因此不应该与订餐服务对应，所以是回答'chat'。

                2.错误输出：
                '''
                服务: 物品配送
                推理: 客人的最新消息询问了“可以寄快递吗”，这表明客人需要将物品寄出，而酒店提供的服务中，“物品配送”服务可以满足客人将物品寄出的需求。虽然客人没有直接提到“物品配送”这个服务名称，但根据客人询问的内容，可以推断出客人需要的服务是将物品寄出，这与“物品配送”服务的描述相匹配。因此，根据客人的询问内容，可以判断客人需要的服务是“物品配送”。
                '''
                错误原因：
                客人需要将物品寄出，酒店提供的“物品配送”服务是向用户配送酒店的用品，无法满足客人将物品寄出的需求，因此不应该与场所预定服务对应，所以是回答'chat'。
                
                3.错误输出：
                '''
                服务: 无
                推理: 客人最新消息只是“我需要。”，没有明确指定任何服务。
                '''
                错误原因：
                客人虽然最新消息只是“我需要。”, 但是根据历史对话记录判断，客人同意预约维修人员上门，属于“报障维修”的服务范围。

                ### 正确的输出举例：
                1.正确输出：
                '''
                服务: 无
                推理: 客人的最新消息并没有明确提出一个新的服务需求，而是在重复之前的询问。同时，酒店提供的服务列表中也没有与它直接相关的服务。所以，根据客人的最新消息和对话历史，无法判断客人明确指定了某个服务。
                '''

                ### 输出格式要求：
                请按照以下格式提供你的输出：
                服务:服务名称
                推理:你的推理过程，包括你是如何根据客人的消息和对话历史来判断的，不多于50字。
                ### Output
            
                """
        aget = Agent(user=User(), user_message='')
        response = await aget.async_interact(prompt=instruction)
        print(response)


if __name__ == "__main__":

    user = User()
    user_message = "我想跟我儿子语音通话"

    result = asyncio.run(NluService.test())

    print(result)

